﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Stub;
using Nvigorate.Data.Query;
using Nvigorate.Relational.Builders;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Mapping.Interface;
using Nvigorate.Relational.Repository;
using Nvigorate.TestObjects;

namespace Nvigorate.Test.Relational.Repository
{
    [TestClass]
    public class SelectBuilderTests
    {
        #region HoldingTable Tests

        [TestMethod]
        public void HoldingTable_UsesTableVariable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof (Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.HoldingTable;

            // Assert
            Assert.AreEqual("Person", table.Table.Name);
            Assert.IsTrue(table.Table.IsVariable);
            Assert.AreEqual(2, table.ColumnList.Count);
            QueryAssert.SchemaColumn(table.ColumnList[0], "SocialSecurityNo", typeof (String));
            QueryAssert.SchemaColumn(table.ColumnList[1], "BirthDate", typeof (DateTime));
        }

        #endregion

        #region InsertIntoHoldingTable Tests

        [TestMethod]
        public void InsertIntoHoldingTable_CreatesInsertTable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.AreEqual("Person", table.Table.Name);
            Assert.AreEqual(2, table.ColumnList.Count);
            var insertColumn = table.ColumnList[0] as InsertColumn;
            Assert.AreEqual("SocialSecurityNo", insertColumn.Name);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_ReadsFromSelectQuery()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual("Person", table.SelectQuery.Table.Name);
            Assert.IsFalse(table.SelectQuery.Table.IsVariable);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_ReadsAllColumnsFromSelectQuery()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual(2, table.ColumnList.Count);
            Assert.AreEqual("SocialSecurityNo", ((Column) table.SelectQuery.ColumnList[0]).Name);
            Assert.AreEqual("BirthDate", ((Column) table.SelectQuery.ColumnList[1]).Name);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_WithNoQualifiersInInstructions_HasNoQualifiersInSelectQuery()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual("Person", table.SelectQuery.Table.Name);
            Assert.IsFalse(table.SelectQuery.Table.IsVariable);
            Assert.AreEqual(2, table.ColumnList.Count);
            Assert.AreEqual(0, table.SelectQuery.Qualifiers.Count);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_UsesPagingFromLoadInstructions()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var loadInstructions = new LoadInstruction<Person>();
            loadInstructions.Page(2).PageBy(10);

            var builder = new SelectBuilder(maps, typeof(Person), null, "", loadInstructions);

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual(1, table.SelectQuery.Qualifiers.Count);
            var qualifiers = table.SelectQuery.Qualifiers;
            var pageQualifier = qualifiers[0] as Page;
            Assert.IsNotNull(pageQualifier);
            Assert.AreEqual(2, pageQualifier.Index);
            Assert.AreEqual(10, pageQualifier.Size);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_UsesCriteriaFromLoadInstructions()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var loadInstructions = new LoadInstruction<Person>();
            loadInstructions.Where(Criterion.Column("SocialSecurity", "").EqualTo("123456789"));

            var builder = new SelectBuilder(maps, typeof(Person), null, "", loadInstructions);

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual(1, table.SelectQuery.Qualifiers.Count);
            var qualifiers = table.SelectQuery.Qualifiers;
            var where = table.SelectQuery.Qualifiers[0] as Where;
            Assert.IsNotNull(where);
            Assert.AreEqual(1, where.SearchCriteria.Count);
            QueryAssert.EqualCriterion(where.SearchCriteria[0].Criteria[0], "SocialSecurity", "123456789");
        }

        [TestMethod]
        public void InsertIntoHoldingTable_UsesOrderFromLoadInstructions()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var loadInstructions = new LoadInstruction<Person>();
            loadInstructions.OrderBy(Order.By().Ascending("SocialSecurity"));

            var builder = new SelectBuilder(maps, typeof(Person), null, "", loadInstructions);

            // Act
            var table = builder.InsertIntoHoldingTable;

            // Assert
            Assert.IsNotNull(table.SelectQuery);
            Assert.AreEqual(1, table.SelectQuery.Qualifiers.Count);
            var order = table.SelectQuery.Qualifiers[0] as Order;
            Assert.AreEqual("SocialSecurity", ((OrderColumn)order.ColumnList[0]).Name);
            Assert.AreEqual(SortOrder.Ascending, ((OrderColumn)order.ColumnList[0]).Order);
        }

        [TestMethod]
        [Ignore]
        // The mocks for this test do not work correctly
        public void InsertIntoHoldingTable_WhenChildSources_IncludesJoin()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex("repositoryName", new[]
                                                                      {
                                                                          CreateMap(typeof(Person), "Person",
                                                                          new []
                                                                              {
                                                                                  CreateChildMap("PersonExtended",
                                                                                  Criterion.Column("Id", "").EqualTo(5))
                                                                              })
                                                                      });

            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var insert = builder.InsertIntoHoldingTable;
            var select = insert.SelectQuery;

            // Assert
            Assert.AreEqual(1, select.Table.JoinedTables.Count);
            Assert.AreEqual("Person", select.Table.Name);
            var joinTable = select.Table.JoinedTables.Peek();
            Assert.AreEqual("PersonExtended", joinTable.Name);
            QueryAssert.EqualCriterion(joinTable.JoinCriteria[0].Criteria[0], "Id", 5);
        }

        [TestMethod]
        public void InsertIntoHoldingTable_WhenNoChildSources_DoesNotIncludeJoin()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var insert = builder.InsertIntoHoldingTable;
            var select = insert.SelectQuery;

            // Assert
            Assert.AreEqual(0, select.Table.JoinedTables.Count);
        }

        #endregion

        #region SelectFromHoldingTable Tests

        [TestMethod]
        public void SelectFromHoldingTable_IsTableVariable()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());

            // Act
            var table = builder.SelectFromHoldingTable;

            // Assert
            Assert.IsTrue(table.Table.IsVariable);
            Assert.AreEqual("Person", table.Table.Name);
        }

        [TestMethod]
        public void SelectFromHoldingTable_HasSameColumnsAsMap()
        {
            // Arrange
            var maps = CreateRepositoryMapIndex();
            var builder = new SelectBuilder(maps, typeof(Person), null, "", new LoadInstruction<Person>());
            var map = maps.GetMap(typeof (Person));
            // Act
            var table = builder.SelectFromHoldingTable;

            // Assert
            Assert.AreEqual(2, table.ColumnList.Count);
            Assert.AreEqual("Person.SocialSecurityNo", ((Column)table.ColumnList[0]).Name);
            Assert.AreEqual("Person.BirthDate", ((Column)table.ColumnList[1]).Name);
            Assert.AreEqual("SocialSecurity", ((Column)table.ColumnList[0]).Alias);
            Assert.AreEqual("DateOfBirth", ((Column)table.ColumnList[1]).Alias);
        }
        #endregion

        #region Private Methods

        public IRepositoryMapIndex CreateRepositoryMapIndex()
        {
            return CreateRepositoryMapIndex("repositoryName", new []{CreateMap(typeof(Person))});
        }

        public IRepositoryMapIndex CreateRepositoryMapIndex(string repositoryName, IEnumerable<IBindingMap> sourceMaps)
        {
            var mock = new Mock<IRepositoryMapIndex>();
            foreach (var map in sourceMaps)
            {
                var targetType = map.TargetType;
                mock.Expect(m => m.GetMap(targetType)).Returns(map);
            }

            return mock.Object;
        }

        public IBindingMap CreateMap(Type type)
        {
            return CreateMap(type, "Person", new List<ISourceMap>());
        }

        public IBindingMap CreateMap(Type type, string sourceName, IEnumerable<ISourceMap> childSources)
        {
            var mock = new Mock<IBindingMap>();
            mock.Stub(m => m.TargetType, typeof(Person));
            var sources = CreateSources(typeof(Person), sourceName);
            mock.Stub(m => m.Sources, sources);
            mock.Expect(m => m.GetChildSources(sourceName)).Returns(childSources);
            mock.Expect(m => m.GetRoot()).Returns(sources[sourceName]);
            mock.Expect(m => m.GetRootName()).Returns(sourceName);
            return mock.Object;
        }

        public ISourceMap CreateChildMap(string childSourceName, SearchCriteria joinCriteria)
        {
            var mock = new Mock<ISourceMap>();
            var mockJoin = new Mock<IJoin>();
            mockJoin.Stub(j => j.JoinCriteria, joinCriteria);
            mock.Stub(m => m.SourceName, childSourceName);
            mock.Stub(m => m.Join, mockJoin.Object);
            return mock.Object;
        }
        private Dictionary<string, ISourceMap> CreateSources(Type type, string sourceName)
        {
            var map = new SourceMap(type.Name) {SourceName = sourceName};
            map.Assignments.Add(
                new MemberAssignment(true, "Person", "SocialSecurityNo", "Person", "SocialSecurity"));
            map.Assignments.Add(
                new MemberAssignment(false, "Person", "BirthDate", "Person", "DateOfBirth"));
            var result = new Dictionary<string, ISourceMap> {{sourceName, map}};
            return result;
        }

        #endregion
    }
}